# Copyright (C) 2005 by Tilburg University, http://www.uvt.nl/.
# Copyright (C) 2005 by Stefan Schlott
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

"""This is a interface to the GnuPGInterface library. It eases the
creation of instances of the interface and handles deadlock problems
using threads. Furthermore, in this way it should be possible to
replace GnuPGInterface with a different one (if ever needed)."""


import re
import os
import threading

import GnuPGInterface

from Foundation import *
from AppKit import *

class AsyncRead(threading.Thread):
    def __init__(self,infile):
        threading.Thread.__init__(self)
        self.infile=infile
        self.data=None
    def run(self):
        self.data = self.infile.read()
        self.infile.close()

class AsyncWrite(threading.Thread):
    def __init__(self,outfile,data):
        threading.Thread.__init__(self)
        self.outfile=outfile
        self.data=data
    def run(self):
        self.outfile.write(self.data)
        self.outfile.close()

class GPGHelper:
    message = None
    def __init__(self, password):
        self.password = password

    def getGPGObject(self):
        gpg = GnuPGInterface.GnuPG()
        gpg.call = "/usr/local/bin/gpg"
        gpg.options.armor = 1
        gpg.options.meta_interactive = 0
        gpg.options.extra_args.append('--no-secmem-warning')
        gpg.options.quiet = 0
        return gpg
    
    def importKey(self,key):
        gpg = self.getGPGObject()
        p = gpg.run(['--import'],create_fhs=['stdin','stdout','stderr'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        p.handles['stdin'].write(key)
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        # Ignore date from t_out
        result = t_err.data
        try:
            p.wait()
        except IOError:
            NSLog('Error importing keys: %s' % result)
            return None
        self.checkPerms()
        key_ids= []
        for line in result.lower().splitlines():
            g = re.search('key ([0-9a-f]+):',line)
            if g!=None:
                key_ids.append('0x%s' % g.groups()[0])
        return key_ids

    def decryptMessage(self,msg):
        gpg = self.getGPGObject()
        plaintext = None
        p = gpg.run(['--decrypt','--no-permission-warning'],
            create_fhs=['stdin','stdout','stderr','status','passphrase'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        t_status = AsyncRead(p.handles['status'])
        t_status.start()
        p.handles['passphrase'].write(self.password)
        p.handles['passphrase'].close()
        p.handles['stdin'].write(msg)
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        t_status.join()
        plaintext = t_out.data
        status = t_status.data
        result = t_err.data
        try:
            p.wait()
        except IOError:
            if (plaintext==None) or (len(plaintext)==0):
                self.message = "Error decrypting message: \n" +result
                return False
            else:
                self.message = "Return code non-zero, but plaintext received: " + result

        # Check signature
        key_ids = []
        key_names = []
        for line in status.splitlines():
            # we are using short keyid to pinpoint keys: last 8 hexbytes of long key id
            g = re.search('^\[GNUPG:\] GOODSIG [0-9A-F]{8}([0-9A-F]{8}) (.*)',line)
            if g!=None:
                key_ids.append('0x%s' % g.groups()[0].lower())
                key_names.append(g.groups()[1])

        if not key_ids:
            self.message = "No good signature found on message: \n" + result
        else:
            self.message = "Valid signature from key(s):\n\n"+ ' '.join(key_names)
        return plaintext

    def decryptFile(self,file):
        gpg = self.getGPGObject()
        plaintext = None
        p = gpg.run(['--no-permission-warning', file],
            create_fhs=['stdin','stdout','stderr','status','passphrase'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        t_status = AsyncRead(p.handles['status'])
        t_status.start()
        p.handles['passphrase'].write(self.password)
        p.handles['passphrase'].close()
        p.handles['stdin'].write("")
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        t_status.join()
        plaintext = t_out.data
        status = t_status.data
        result = t_err.data
        try:
            p.wait()
        except IOError:
            self.message = "Error decrypting message: \n" +result

        # Check signature
        key_ids = []
        key_names = []
        for line in status.splitlines():
            # we are using short keyid to pinpoint keys: last 8 hexbytes of long key id
            g = re.search('^\[GNUPG:\] GOODSIG [0-9A-F]{8}([0-9A-F]{8}) (.*)',line)
            if g!=None:
                key_ids.append('0x%s' % g.groups()[0].lower())
                key_names.append(g.groups()[1])

        if not key_ids:
            self.message = "No good signature found on message: \n" + result
        else:
            self.message = "Valid signature from key(s):\n\n"+ ' '.join(key_names)
        return True

    def encryptMessage(self,msg,recipients):
        gpg = self.getGPGObject()
        params = ['--encrypt','--always-trust','--batch','--no-permission-warning']
        for i in recipients:
            params.append('-r')
            params.append(i)
        p = gpg.run(params, create_fhs=['stdin','stdout','stderr'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        p.handles['stdin'].write(msg)
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        ciphertext = t_out.data
        result = t_err.data
        try:
            p.wait()
        except IOError:
            self.message = "Error encrypting message: \n" + result
            return False
        self.message = ciphertext
        return True

    def encryptSignMessage(self,msg,recipients):
        gpg = self.getGPGObject()
        params = ['--encrypt','--sign','--always-trust','--batch','--no-permission-warning']
        for i in recipients:
            params.append('-r')
            params.append(i)
        p = gpg.run(params, create_fhs=['stdin','stdout','stderr','passphrase'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        p.handles['passphrase'].write(self.password)
        p.handles['passphrase'].close()
        p.handles['stdin'].write(msg)
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        ciphertext = t_out.data
        result = t_err.data
        try:
            p.wait()
        except IOError:
            self.message = "Error encrypting message: \n" + result
            return False
        self.message = ciphertext
        return True

    def encryptSignFile(self,file,recipients):
        gpg = self.getGPGObject()
        gpg.options.armor = 0
        params = ['--encrypt','--sign','--always-trust','--batch','--no-permission-warning']
        for i in recipients:
            params.append('-r')
            params.append(i)
        params.append(file)
        p = gpg.run(params, create_fhs=['stdin','stdout','stderr','passphrase'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        p.handles['passphrase'].write(self.password)
        p.handles['passphrase'].close()
        p.handles['stdin'].write("")
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        ciphertext = t_out.data
        result = t_err.data
        try:
            p.wait()
        except IOError:
            self.message = "Error encrypting message: \n" + result
            return False
        self.message = ciphertext
        return True

    def signFile(self,file):
        gpg = self.getGPGObject()
        params = ['--sign','--always-trust','--batch','--no-permission-warning']
        params.append(file)
        p = gpg.run(params, create_fhs=['stdin','stdout','stderr','passphrase'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        p.handles['passphrase'].write(self.password)
        p.handles['passphrase'].close()
        p.handles['stdin'].write("")
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        ciphertext = t_out.data
        result = t_err.data
        try:
            p.wait()
        except IOError:
            self.message = "Error signing message: \n" + result
            return False
        self.message = ciphertext
        return True


    def signMessage(self,msg):
        gpg = self.getGPGObject()
        params = ['--clearsign','--batch','--no-permission-warning']
        p = gpg.run(params, create_fhs=['stdin','stdout','stderr','passphrase'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        p.handles['passphrase'].write(self.password)
        p.handles['passphrase'].close()
        p.handles['stdin'].write(msg)
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        ciphertext = t_out.data
        result = t_err.data
        try:
            p.wait()
        except IOError:
            self.message = "Error encrypting message:\n" + result +"\n" + ciphertext
            return False
        self.message = ciphertext
        return True

    def verifyMessage(self,msg):
        gpg = self.getGPGObject()
        args = []

        params = ['--verify','--always-trust','--batch','--no-permission-warning']
        # specify stdout too: we don't want to clutter this proces's stdout
        p = gpg.run(params, args=args, create_fhs=['stdin', 'stdout','stderr','status'])
        # see gnupg/DETAILS in the gnupg package for info on status fd
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        t_status = AsyncRead(p.handles['status'])
        t_status.start()
        p.handles['stdin'].write(msg)
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        t_status.join()
        result = t_err.data
        status = t_status.data
        try:
            p.wait()
        except IOError:
            self.message = "Error verifying message: \n\n" + result
            NSLog(result)
            return False

        key_ids = []
        key_names = []
        for line in status.splitlines():
            # we are using short keyid to pinpoint keys: last 8 hexbytes of long key id
            g = re.search('^\[GNUPG:\] GOODSIG [0-9A-F]{8}([0-9A-F]{8}) (.*)',line)
            if g!=None:
                key_ids.append('0x%s' % g.groups()[0].lower())
                key_names.append(g.groups()[1])

        if not key_ids:
            self.message = "No good signature found on message: \n" + status + "\n\n" +result
        else:
            self.message = "Valid signature from key(s):\n\n"+ ' '.join(key_names)
        return True

    def verifyFile(self,file):
        gpg = self.getGPGObject()
        args = []

        params = ['--verify','--always-trust','--batch','--no-permission-warning', file]
        # specify stdout too: we don't want to clutter this proces's stdout
        p = gpg.run(params, args=args, create_fhs=['stdin', 'stdout','stderr','status'])
        # see gnupg/DETAILS in the gnupg package for info on status fd
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        t_status = AsyncRead(p.handles['status'])
        t_status.start()
        p.handles['stdin'].write("")
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        t_status.join()
        result = t_err.data
        status = t_status.data
        try:
            p.wait()
        except IOError:
            self.message = "Error verifying message: \n\n" + result
            NSLog(result)
            return False

        key_ids = []
        key_names = []
        for line in status.splitlines():
            # we are using short keyid to pinpoint keys: last 8 hexbytes of long key id
            g = re.search('^\[GNUPG:\] GOODSIG [0-9A-F]{8}([0-9A-F]{8}) (.*)',line)
            if g!=None:
                key_ids.append('0x%s' % g.groups()[0].lower())
                key_names.append(g.groups()[1])

        if not key_ids:
            self.message = "No good signature found on message: \n" + status + "\n\n" +result
        else:
            self.message = "Valid signature from key(s):\n\n"+ ' '.join(key_names)
        return True
        
        
    def exportingKey(self):
        gpg = self.getGPGObject()
        plaintext = None
        p = gpg.run(['--list-secret-keys','--no-permission-warning'],
            create_fhs=['stdin','stdout','stderr','status'])
        t_out = AsyncRead(p.handles['stdout'])
        t_out.start()
        t_err = AsyncRead(p.handles['stderr'])
        t_err.start()
        t_status = AsyncRead(p.handles['status'])
        t_status.start()
        p.handles['stdin'].write("")
        p.handles['stdin'].close()
        t_out.join()
        t_err.join()
        t_status.join()
        plaintext = t_out.data
        status = t_status.data
        result = t_err.data
        try:
            p.wait()
        except IOError:
            self.message = "Error in first pass: " + result
            return False
        # Check signature
        key_ids = []
        for line in plaintext.splitlines():
            # we are using short keyid to pinpoint keys: last 8 hexbytes of long key id
            #sec   1024D/63BF0A49 2006-12-30 [expires: 2008-12-30]
            g = re.search('^sec.+?[0-9A-F]+/([0-9A-F]{8})',line)
            if g!=None:
                key_ids.append('0x%s' % g.groups()[0].lower())
        if not key_ids:
            self.message = "No keys found: \n" + result
            return False
        else:
            plaintext = None
            p = gpg.run(['--export','--no-permission-warning', ' '.join(key_ids)],
                create_fhs=['stdin','stdout','stderr','status'])
            t_out = AsyncRead(p.handles['stdout'])
            t_out.start()
            t_err = AsyncRead(p.handles['stderr'])
            t_err.start()
            t_status = AsyncRead(p.handles['status'])
            t_status.start()
            p.handles['stdin'].write("")
            p.handles['stdin'].close()
            t_out.join()
            t_err.join()
            t_status.join()
            plaintext = t_out.data
            status = t_status.data
            result = t_err.data
            try:
                p.wait()
            except IOError:
                self.message = "Error in first pass: " + result
                return False
            self.message = plaintext
        return True